/*
 * Route.java
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.core.transit;

import org.fpdev.core.routenet.RouteNetwork;
import java.io.File;
import java.io.FileWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.fpdev.core.FPEngine;
import org.fpdev.core.Scenario;
import org.fpdev.core.basenet.BNode;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedMultigraph;
import org.jgrapht.traverse.TopologicalOrderIterator;

public class Route {

  private String id_,  name_;
  private Scenario scenario_;
  private boolean showID_;
  private Provider provider_;
  private SortedMap<String, SubRoute> subs_;

  public Route(Scenario s, Provider p, String id) {
    this(s, p, id, "", true);
  }

  public Route(Scenario s, Provider p, String id, String name) {
    this(s, p, id, name, true);
  }

  public Route(Scenario s, Provider p, String id, String name, boolean showID) {
    scenario_ = s;
    provider_ = p;
    id_ = id;
    name_ = name;
    //subs_ = new RedBlackTree(new ComparatorExtender(java.text.Collator.getInstance()));
    subs_ = new TreeMap<String, SubRoute>();
    showID_ = showID;
  }

  public Route(Route rte, FPEngine engine) {
    scenario_ = rte.scenario_;
    provider_ = rte.provider_;
    id_ = rte.id_;
    name_ = rte.name_;
    subs_ = new TreeMap<String, SubRoute>();
    for(SubRoute sub : rte.getSubRoutes())
      subs_.put(sub.getID(), new SubRoute(sub, engine));
    showID_ = rte.showID_;

  }

  public Scenario getScenario() {
    return scenario_;
  }

  public Provider getProvider() {
    return provider_;
  }

  public void setProvider(Provider p) {
    provider_ = p;
  }

  public void setID(String id) {
    id_ = id;
  }

  public String getID() {
    return id_;
  }

  /**
   * Strips any scenario specifier off the end of the Route ID. For instance,
   * for a scenario-specific route with ID "ABC~2", the stripped ID would be
   * simply "ABC".   
   *   
   * @return the stripped ID
   */
  public String getStrippedID() {
    if (id_.indexOf("~") >= 0) {
      //System.out.println("strID "+id_.split("~",1)[0]);
      return id_.substring(0, id_.indexOf("~"));
    }
    return id_;
  }

  /**
   * Returns the "combined" ID of provider ID + route ID
   * 
   * @return the combined id
   */
  public String getCombinedID() {
    return provider_.getID() + id_;
  }

  /**
   * Returns this Route's combined ID, stripped of any scenario suffix. See
   * more at getCombinedID() and getStrippedID().
   * 
   * @return the stripped combined ID
   */
  public String getStrippedCombinedID() {
    return provider_.getID() + getStrippedID();
  }

  public boolean getShowID() {
    return showID_;
  }

  public void setShowID(boolean showID) {
    showID_ = showID;
  }

  public String getName() {
    return name_;
  }

  public void setName(String name) {
    name_ = name;
  }

  public TransitPath.Type getRepresentativeType() {
    return getSubRoutes().iterator().next().getPath().getType();
  }

  public int addSubRoute(SubRoute sub) {
    subs_.put(sub.getID(), sub);
    Iterator iter = subs_.values().iterator();
    int index = 0;
    while (iter.next() != sub) {
      index++;
    }
    return index;
  }

  public SubRoute getSubRoute(String id) {
    return subs_.get(id);
  }

  public int subRouteCount() {
    return subs_.size();
  }

  public int subRouteWithTimetablesCount() {
    int c = 0;
    for (SubRoute sub : getSubRoutes())
      if (sub.timeTableCount() > 0) c++;
    return c;
  }

  public Collection<SubRoute> getSubRoutes() {
    return subs_.values();
  }

  public void clearSubRoutes(RouteNetwork rteNet) {
    while (!subs_.isEmpty()) {
      deleteSubRoute(subs_.firstKey(), rteNet);
    }
  }

  public void deleteSubRoute(String id, RouteNetwork rteNet) {
    SubRoute sub = subs_.get(id);
    sub.clearRouteLinks(rteNet);
    sub.delete();
    subs_.remove(id);
  }

  public void subRouteIDChanged(SubRoute sub, String oldID) {
    subs_.remove(oldID);
    subs_.put(sub.getID(), sub);
  }

  public void initWebFiles(FPEngine engine, String path) {
    /*initWebFiles(engine, Timetable.DayOfWeek.WEEKDAY, path);
    initWebFiles(engine, Timetable.DayOfWeek.SATURDAY, path);
    initWebFiles(engine, Timetable.DayOfWeek.SUNDAY, path);*/
    for(CalendarService service : engine.getRoutes().getCalendarServices().getServices())
      initWebFiles(engine, service, path);
  }

  private void initWebFiles(FPEngine engine, CalendarService service, String path) {
    //System.out.println("iWF day="+day);
    Set<String> dirSet = new HashSet<String>();
    dirSet.add("NB");
    dirSet.add("SB");
    dirSet.add("EB");
    dirSet.add("WB");
    dirSet.add("IB");
    dirSet.add("OB");
    dirSet.add("CW");
    dirSet.add("CCW");
    dirSet.add("RT");
    dirSet.add("A");
    dirSet.add("B");

    Map<String, List<SubRoute>> dirMap = new HashMap<String, List<SubRoute>>();
    for(String dir : dirSet)
      dirMap.put(dir, new LinkedList<SubRoute>());

    for(SubRoute sub : getSubRoutes()) {
      //if(sub == null) System.out.println("null sub, route id=");
      if (sub.hasTable(service)) {
        dirMap.get(sub.getDirCode().toUpperCase()).add(sub);
      }
    }

    for(String dir : dirSet) {
      //System.out.println(" iWF dir: "+dir);
      List<SubRoute> subList = dirMap.get(dir);
      if (subList.size() > 0) {
        initWebFile(engine, service, dir, subList, path);
      }
    }
  }

  private void initWebFile(FPEngine engine, CalendarService service, String dir, List<SubRoute> subList, String path) {
    String filename = path + File.separator + provider_.getID() + id_ + "_" + dir.toLowerCase() + "_" + service.getID() + ".xml";

    // PROCESS TIMEPOINTS

    LinkedList<String> allTPts = new LinkedList<String>();
    Iterator<SubRoute> subs = subList.iterator();

    // create map of suffixed timept lists

    Map<String, List<String>> suffixedTPtMap = new HashMap<String, List<String>>();
    while (subs.hasNext()) {
      SubRoute sub = subs.next();
      //sub.getTable(0).
      Iterator<TimePoint> tpts = sub.getPath().getTimePoints();
      Map<Integer, Integer> timesEnc = new HashMap<Integer, Integer>();

      List<String> suffixedList = new LinkedList<String>();
      while (tpts.hasNext()) {
        TimePoint tpt = tpts.next();
        int tptNodeID = tpt.getNode().getID();
        if (timesEnc.containsKey(tptNodeID)) {
          timesEnc.put(tptNodeID, timesEnc.get(tptNodeID).intValue() + 1);
        } else {
          timesEnc.put(tptNodeID, 1);
        }
        int te = timesEnc.get(tptNodeID);
        String suffixedID = tptNodeID + "_" + (te < 10 ? "0" : "") + te;
        suffixedList.add(suffixedID);
      }

      suffixedTPtMap.put(sub.getID(), suffixedList);
    }

    // initialize map of timept-subroute associations
    Map<String, List<String>> tptRouteLookup = new HashMap<String, List<String>>();

    // create merged timept list 

    DirectedMultigraph<String, DefaultEdge> tptGraph =
            new DirectedMultigraph<String, DefaultEdge>(DefaultEdge.class);
    subs = subList.iterator();
    while (subs.hasNext()) {
      SubRoute sub = subs.next();
      Iterator<String> tpts = suffixedTPtMap.get(sub.getID()).iterator();
      //System.out.println("SUB1: "+sub.getID());
      String lastID = null;
      while (tpts.hasNext()) {
        String suffixedID = tpts.next();

        if (lastID != null) {
          if (!tptGraph.containsVertex(lastID)) {
            tptGraph.addVertex(lastID);
          }
          if (!tptGraph.containsVertex(suffixedID)) {
            tptGraph.addVertex(suffixedID);
          }
          tptGraph.addEdge(lastID, suffixedID);
        }
        // add to timept-subroute lookup table
        if (tptRouteLookup.containsKey(suffixedID)) {
          tptRouteLookup.get(suffixedID).add(sub.getID());
        } else {
          List<String> newList = new LinkedList<String>();
          newList.add(sub.getID());
          tptRouteLookup.put(suffixedID, newList);
        }
        lastID = suffixedID;
      }
    }

    // perform top-sort

    TopologicalOrderIterator<String, DefaultEdge> top = new TopologicalOrderIterator<String, DefaultEdge>(tptGraph);
    while (top.hasNext()) {
      allTPts.add(top.next());
    }

    // write combined timept list to xml file
    String tptsXml = "<timepts>\n";
    Iterator<String> tptIDs = allTPts.iterator();
    while (tptIDs.hasNext()) {
      String tptID = tptIDs.next();
      //System.out.println("- tpt: "+tptID);
      String nodeID = tptID.substring(0, tptID.length() - 3);
      BNode node = engine.getNodeFromID(new Integer(nodeID));
      String subsStr = "";
      Iterator<String> subIDs = tptRouteLookup.get(tptID).iterator();
      while (subIDs.hasNext()) {
        subsStr += subIDs.next().toLowerCase();
        if (subIDs.hasNext()) {
          subsStr += ",";
        }
      }
      tptsXml += "<timept subs=\"" + subsStr + "\">" + node.getDisplayName(engine.getBaseNet()) + "</timept>\n";
    }
    tptsXml += "</timepts>\n";

    // PROCESS RUNS

    List<RunXmlLine> runLines = new LinkedList<RunXmlLine>();
    int combinedRunCount = 0;
    subs = subList.iterator();
    boolean dbg = false;// (day == CTimeTable.WEEKDAY && dir.equals("NB"));
    while (subs.hasNext()) {
      SubRoute sub = subs.next();
      if (!sub.hasTable(service)) {
        continue;
      }
      if (dbg) {
        System.out.println("sub: " + sub.getID());
      }
      combinedRunCount += sub.getRunCount(service);
      List<String> tptIDList = suffixedTPtMap.get(sub.getID());
      Iterator<String> idIter = tptIDList.iterator();

      // create mapping of this sub's timepoints to indices in combined list
      /*Map<String, Integer> tptIndices = new HashMap<String, Integer>();
      while(idIter.hasNext()) {
      String suffixedID = idIter.next();
      tptIndices.put(suffixedID, allTPts.indexOf(suffixedID));
      }*/

      Timetable table = sub.getTable(service);
      for (int r = 0; r < table.runCount(); r++) {
        if (dbg) {
          System.out.println(" r=" + r);
        }
        RunXmlLine line = new RunXmlLine(sub);
        int tptCount = sub.getPath().getTimePointCount();
        idIter = tptIDList.iterator();
        int t = 1;
        int lastCombinedIndex = 0;
        while (idIter.hasNext()) {
          //for(int t=1; t <= tptCount; t++) {
          String suffixedID = idIter.next();
          if (dbg) {
            System.out.println("  suffixedID: " + suffixedID);
          }
          int combinedIndex = allTPts.indexOf(suffixedID);
          if (dbg) {
            System.out.println("ci/lci: " + combinedIndex + " " + lastCombinedIndex);
          }
//CTimePoint tpt = sub.getPath().getTimePoint(t);
          int stopIndex = sub.getPath().getMasterIndex(t);
          int time = table.getTime(r, stopIndex);
          line.addCommas(combinedIndex - lastCombinedIndex);
          line.addTime(time);
          lastCombinedIndex = combinedIndex;
          t++;
        }
        line.addCommas(allTPts.size() - 1 - lastCombinedIndex);
        runLines.add(line);
      }
    }

    // sort lines and write list to xml
    Collections.sort(runLines);
    String runsXml = "<runs>\n";
    Iterator<RunXmlLine> lines = runLines.iterator();
    while (lines.hasNext()) {
      runsXml += lines.next().getLine();
    }
    runsXml += "</runs>\n";

    try {
      //System.out.println("  writing file: "+filename);
      FileWriter writer = new FileWriter(filename);

      writer.write("<?xml version=\"1.0\"?>\n");
      writer.write("<route runs=\"" + combinedRunCount + "\" timepts=\"" + allTPts.size() + "\">\n");
      writer.write(tptsXml);
      writer.write(runsXml);
      writer.write("</route>\n");
      writer.close();

    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /*private String dayStr(int day) {
    switch (day) {
      case Timetable.WEEKDAY:
        return "w";
      case Timetable.SATURDAY:
        return "sat";
      case Timetable.SUNDAY:
        return "sun";
    }
    return null;
  }*/

  private class RunXmlLine implements Comparable {

    private int time_;
    private String content_;
    private String subID_;

    public RunXmlLine(SubRoute sub) {
      subID_ = sub.getID().toLowerCase();
      content_ = "";
      time_ = -1;
    }

    public String getLine() {
      return "<run sub=\"" + subID_ + "\">" + content_ + "</run>\n";
    }

    public void addCommas(int number) {
      for (int i = 0; i < number; i++) {
        content_ += ",";
      }
    }

    public void addTime(int time) {
      content_ += time;
      if (time_ == -1) {
        time_ = time;
      }
    }

    public int compareTo(Object o) {
      return new Integer(time_).compareTo(new Integer(((RunXmlLine) o).time_));
    }
  }

  public SerializableRoute getSerializable() {
    List<SerializableSubRoute> subs = new LinkedList<SerializableSubRoute>();
    Iterator<SubRoute> subIter = subs_.values().iterator();
    while (subIter.hasNext()) {
      subs.add(subIter.next().getSerializable());
    }

    return new SerializableRoute(id_, showID_, name_, provider_.getID(), scenario_.getID(), subs);
  }
}




















